"""
数据报表页面接口（参照旧代码格式和逻辑）
监测类型： validators/reports: ListenType
"""

import traceback
import arrow

from flask import g
from bson import ObjectId
from mongoengine import Q

from app.libs.redprint import RedPrint
from app.db.models.tasks import Task
from app.db.models.jobs import Job
from app.db.models.asset_tasks import AssetTasks
from app.db.models.wass import ScanEvents
from app.db.models.users import User
from app.db.models.ipv6_check_urls import IPv6CheckUrls

from flask import jsonify, request, current_app as app
from app.api.v2.common import param_input
from app.validators.repodatas import TaskSchema
from app.errors import ParameterError
from app.libs.public_funcs import get_ipv6_ava_results
from app.libs.enums import DateRange, TaskStatus, TASK_TYPE_TUPLE
from app.libs.score import Score
from app.serializers import SimpleSerializer
from app.libs.ipv6_check import parse_ipv6_event, parse_ipv6_website_support_check


api = RedPrint('repodatas')


def query_data(obj, query, resp_fields=None, limit=None, sort=None):
    """公共代码： 数据筛选与返回"""
    data = obj.find(query)
    count = data.count()  # 数据总量

    if sort:
        data = data.order_by(*sort)

    try:
        data = data.only(*resp_fields)
    except Exception as e:
        app.logger.exception(e)
        traceback.print_exc()
        raise ParameterError(msg='请检查返回字段')

    if limit is not None:
        data = data[:limit]

    return list(data.as_pymongo()), count


@api.route('/report-task')
@param_input(TaskSchema, exclude_none=True, by_alias=True)
def report_task(query):
    limit = query.pop('limit', 10)

    query['status'] = TaskStatus.completed.value
    if g.role.name != '管理员':
        query['uid'] = g.user.id

    date_range = query.pop('range', DateRange.last.value)
    if date_range == DateRange.range.last.value:
        pass
    elif date_range == DateRange.range.prev.value:
        pass
    elif date_range == DateRange.range.value:
        start_time = arrow.get(query.pop('startTime')).datetime
        end_time = arrow.get(query.pop('endTime')).datetime
        query['endTime__gte'] = start_time
        query['endTime__lte'] = end_time
    else:
        raise ParameterError(msg=f"range: '{date_range}' not a valid value.")

    task_data_list = []
    origin_data = Task.objects.filter(**query).order_by('-endTime')
    if date_range != DateRange.range.value:
        origin_data = origin_data.limit(7)

    for task in origin_data:
        task_data_list.append(SimpleSerializer(task).data)
    if g.entity_type == "app":
        return jsonify({'code': 200, 'status': 'success', 'data': {'TaskCount': 7, 'TaskData': task_data_list}})
    return jsonify({'code': 200, 'status': 'success', 'TaskCount': 7, 'TaskData': task_data_list})


@api.route('/jobone', methods=['GET'])
def jobone():
    """获取单个job"""
    job_id = request.args.get('jobId')
    q = {'uid': g.audit_uid, '_id': ObjectId(job_id)}
    project_fields = [
        '_id', 'assetId', 'note', 'targetUrl', 'target.host', 'target.protocol', 'sourceIp', 'ignoredWarnIdMap',
        'httpSettings.name', 'httpSettings.target', 'httpSettings.interval',
        'pingSettings.name', 'pingSettings.target', 'pingSettings.interval',

        'vulResultId', 'vulPrevResultId', 'vulResult._id', 'vulResult.name', 'vulResult.target', 'vulResult.status',
        'vulResult.startTime', 'vulResult.endTime', 'vulResult.result.warnings._id', 'vulResult.result.warnings.id',
        'vulResult.result.warnings.title', 'vulResult.result.warnings.level', 'vulResult.result.warnings.category',
        'vulResult.result.warnings.affects', 'vulResult.result.warnings.detail', 'vulResult.result.warnings.isResolved',
        'vulResult.result.warningCount', 'vulResult.result.warnings.ignoredTime', 'vulResult.result.addition',
        'vulResult.result.targetStatus',

        'vulPrevResult._id', 'vulPrevResult.name', 'vulPrevResult.target', 'vulPrevResult.startTime',
        'vulPrevResult.status', 'vulPrevResult.endTime', 'vulPrevResult.result.wranings._id',
        'vulPrevResult.result.wranings.id', 'vulPrevResult.result.wranings.title',
        'vulPrevResult.result.wranings.level', 'vulPrevResult.result.warningCount',
        'vulPrevResult.result.wranings.category', 'vulPrevResult.result.wranings.affects',
        'vulPrevResult.result.wranings.isResolved', 'vulPrevResult.result.wranings.ignoredTime',
        'vulPrevResult.result.addition',

        'sslResultId', 'sslPrevResultId', 'sslResult._id', 'sslResult.name', 'sslResult.target', 'sslResult.startTime',
        'sslResult.endTime', 'sslResult.status', 'sslResult.result.warnings._id', 'sslResult.result.warnings.id',
        'sslResult.result.warnings.title', 'sslResult.result.warnings.level', 'sslResult.result.warnings.category',
        'sslResult.result.warnings.affects', 'sslResult.result.warnings.detail', 'sslResult.result.warningCount',
        'sslResult.result.warnings.isResolved', 'sslResult.result.addition', 'sslResult.result.targetStatus',

        'sslPrevResult._id', 'sslPrevResult.name', 'sslPrevResult.target', 'sslPrevResult.startTime',
        'sslPrevResult.status', 'sslPrevResult.endTime', 'sslPrevResult.result.warnings._id',
        'sslPrevResult.result.warnings.id', 'sslPrevResult.result.warnings.title',
        'sslPrevResult.result.warnings.level', 'sslPrevResult.result.warnings.category',
        'sslPrevResult.result.warnings.affects', 'sslPrevResult.result.warnings.isResolved',
        'sslPrevResult.result.warnings.ignoredTime',

        'securityEventResultId', 'securityEventPrevResultId', 'securityEventResult._id', 'securityEventResult.status',
        'securityEventResult.name', 'securityEventResult.target', 'securityEventResult.startTime',
        'securityEventResult.endTime', 'securityEventResult.result.warnings._id',
        'securityEventResult.result.warnings.id', 'securityEventResult.result.warnings.title',
        'securityEventResult.result.warnings.target', 'securityEventResult.result.warnings.level',
        'securityEventResult.result.warnings.category', 'securityEventResult.result.warnings.affects',
        'securityEventResult.result.warnings.detail', 'securityEventResult.result.warnings.isResolved',
        'securityEventResult.result.warnings.ignoredTime', 'securityEventResult.result.addition',
        'securityEventResult.result.targetStatus',

        'securityEventPrevResult._id', 'securityEventPrevResult.name', 'securityEventPrevResult.target',
        'securityEventPrevResult.status', 'securityEventPrevResult.startTime', 'securityEventPrevResult.endTime',
        'securityEventPrevResult.result.warnings._id', 'securityEventPrevResult.result.warnings.id',
        'securityEventPrevResult.result.warnings.title', 'securityEventPrevResult.result.warnings.level',
        'securityEventPrevResult.result.warnings.category', 'securityEventPrevResult.result.warnings.affects',
        'securityEventPrevResult.result.warnings.isResolved', 'securityEventPrevResult.result.warnings.ignoredTime',
        'securityEventPrevResult.result.addition',

        'contentResultId', 'contentPrevResultId', 'contentResult._id', 'contentResult.name', 'contentResult.target',
        'contentResult.status', 'contentResult.startTime', 'contentResult.endTime', 'contentResult.result.warnings._id',
        'contentResult.result.warnings.id', 'contentResult.result.warnings.title',
        'contentResult.result.warnings.level', 'contentResult.result.warnings.category',
        'contentResult.result.warnings.affects', 'contentResult.result.warnings.detail',
        'contentResult.result.warnings.isResolved', 'contentResult.result.warnings.ignoredTime',
        'contentResult.result.addition', 'contentResult.result.targetStatus',

        'contentPrevResult._id', 'contentPrevResult.name', 'contentPrevResult.target', 'contentPrevResult.status',
        'contentPrevResult.startTime', 'contentPrevResult.status','contentPrevResult.endTime',
        'contentPrevResult.result.warnings._id', 'contentPrevResult.result.warnings.id',
        'contentPrevResult.result.warnings.title', 'contentPrevResult.result.warnings.level',
        'contentPrevResult.result.warnings.category', 'contentPrevResult.result.warnings.affects',
        'contentPrevResult.result.warnings.isResolved', 'contentPrevResult.result.warnings.ignoredTime',
        'contentPrevResult.result.addition',

        'httpResultId', 'httpResult._id', 'httpResult.name', 'httpResult.target', 'httpResult.startTime',
        'httpResult.status', 'httpResult.taskSettings',
        'httpResult.endTime', 'httpResult.result.warnings._id', 'httpResult.result.warnings.id',
        'httpResult.result.warnings.title', 'httpResult.result.warnings.level', 'httpResult.result.warnings.category',
        'httpResult.result.warnings.affects', 'httpResult.result.warnings.detail',
        'httpResult.result.warnings.isResolved', 'httpResult.result.warnings.ignoredTime',
        'httpResult.result.warningCount', 'httpResult.result.addition',

        'httpPrevResultId', 'httpPrevResult._id', 'httpPrevResult.name', 'httpPrevResult.target',
        'httpPrevResult.status', 'httpPrevResult.taskSettings',
        'httpPrevResult.startTime', 'httpPrevResult.endTime', 'httpPrevResult.result.warnings._id',
        'httpPrevResult.result.warnings.id', 'httpPrevResult.result.warnings.title',
        'httpPrevResult.result.warnings.level', 'httpPrevResult.result.warnings.category',
        'httpPrevResult.result.warnings.affects', 'httpPrevResult.result.warnings.isResolved',
        'httpPrevResult.result.warnings.ignoredTime', 'httpPrevResult.result.addition',

        'pingResultId', 'pingResult._id', 'pingResult.name', 'pingResult.target', 'pingResult.startTime',
        'pingResult.status', 'pingResult.taskSettings',
        'pingResult.endTime', 'pingResult.result.wranings._id', 'pingResult.result.wranings.id',
        'pingResult.result.wranings.title', 'pingResult.result.wranings.level',
        'pingResult.result.wranings.category', 'pingResult.result.wranings.affects',
        'pingResult.result.wranings.detail', 'pingResult.result.warningCount',
        'pingResult.result.wranings.isResolved', 'pingResult.result.wranings.ignoredTime',

        'pingPrevResultId',  'pingPrevResult._id', 'pingPrevResult.name', 'pingPrevResult.target',
        'pingPrevResult.status', 'pingPrevResult.taskSettings',
        'pingPrevResult.startTime', 'pingPrevResult.endTime', 'pingPrevResult.result.warnings._id',
        'pingPrevResult.result.warnings.id', 'pingPrevResult.result.warnings.title',
        'pingPrevResult.result.warnings.level', 'pingPrevResult.result.warnings.category',
        'pingPrevResult.result.warnings.affects', 'pingPrevResult.result.warnings.isResolved',
        'pingPrevResult.result.warnings.ignoredTime', 'pingPrevResult.result.addition',

        'changeCheckResultId', 'changeCheckPrevResultId', 'changeCheckResult._id', 'changeCheckResult.name',
        'changeCheckResult.target', 'changeCheckResult.status',
        'changeCheckResult.startTime', 'changeCheckResult.endTime', 'changeCheckResult.result.warnings._id',
        'changeCheckResult.result.warnings.id',
        'changeCheckResult.result.warnings.title', 'changeCheckResult.result.warnings.level',
        'changeCheckResult.result.warnings.category',
        'changeCheckResult.result.warnings.affects', 'changeCheckResult.result.warnings.detail',
        'changeCheckResult.result.warnings.isResolved',
        'changeCheckResult.result.warningCount', 'changeCheckResult.result.warnings.ignoredTime',
        'changeCheckResult.result.addition',
        'changeCheckResult.result.targetStatus',
    ]

    aggregate = [
        {'$match': q},
        {'$lookup': {'from': "tasks", 'localField': "pingPrevResultId", 'foreignField': "_id", 'as': "pingPrevResult"}},
        {'$lookup': {'from': "tasks", 'localField': "contentPrevResultId", 'foreignField': "_id",
                     'as': "contentPrevResult"}},
        {'$lookup': {'from': "tasks", 'localField': "contentResultId", 'foreignField': "_id", 'as': "contentResult"}},
        {'$lookup': {'from': "tasks", 'localField': "httpPrevResultId", 'foreignField': "_id", 'as': "httpPrevResult"}},
        {'$lookup': {'from': "tasks", 'localField': "httpResultId", 'foreignField': "_id", 'as': "httpResult"}},
        {'$lookup': {'from': "tasks", 'localField': "pingPrevResultId", 'foreignField': "_id", 'as': "pingPrevResult"}},
        {'$lookup': {'from': "tasks", 'localField': "pingResultId", 'foreignField': "_id", 'as': "pingResult"}},
        {'$lookup': {'from': "tasks", 'localField': "securityEventPrevResultId", 'foreignField': "_id",
                     'as': "securityEventPrevResult"}},
        {'$lookup': {'from': "tasks", 'localField': "securityEventResultId", 'foreignField': "_id",
                     'as': "securityEventResult"}},
        {'$lookup': {'from': "tasks", 'localField': "sslResultId", 'foreignField': "_id",
                     'as': "sslResult"}},
        {'$lookup': {'from': "tasks", 'localField': "vulResultId", 'foreignField': "_id",
                     'as': "vulResult"}},
        {'$lookup': {'from': "tasks", 'localField': "changeCheckResultId", 'foreignField': "_id",
                     'as': "changeCheckResult"}},
        # {'$lookup': {'from': "tasks", 'localField': "assetId", 'foreignField': "refId", 'as': "asset"}},

        {'$project': {field: 1 for field in project_fields}}
    ]
    jobs = list(Job.objects.aggregate(aggregate))

    if jobs:
        job = jobs[0]
    else:
        job = {}

    # 状态不为 completed时 里面的数据都默认不展示， 默认返回空字典

    if job.get('assetId'):  # lookup 数据量大时不适用
        assets = Task.objects.filter(refId=job['assetId']).only(
            'endTime', 'result.warningCount', 'result.warnings', 'status').order_by('-endTime').limit(2)

        asset_results = [item if item.get('status') == 'completed' else {} for item in assets.as_pymongo()]

        if not asset_results:
            job['asset'] = {'result': {}, 'prevResult': {}}

        elif len(asset_results) == 1:
            job['asset'] = {'result': asset_results[0], 'prevResult': {}}

        elif len(asset_results) >= 2:
            job['asset'] = {'result': asset_results[0], 'prevResult': asset_results[1]}

    # 这两个字段需要以字典形式返回  每条数据都判断是否是completed 否则返回空字典
    job['vulPrevResult'] = job['vulPrevResult'][0] if job.get('vulPrevResult') and job['vulPrevResult'][0].get('status') == 'completed' else {}
    job['vulResult'] = job['vulResult'][0] if job.get('vulResult') and job['vulResult'][0].get('status') == 'completed' else {}

    job['sslPrevResult'] = job['sslPrevResult'][0] if job.get('sslPrevResult') and job['sslPrevResult'][0].get('status') == 'completed' else {}
    job['sslResult'] = job['sslResult'][0] if job.get('sslResult') and job['sslResult'][0].get('status') == 'completed' else {}

    job['contentPrevResult'] = job['contentPrevResult'][0] if job.get('contentPrevResult') and job['contentPrevResult'][0].get('status') == 'completed' else {}
    job['contentResult'] = job['contentResult'][0] if job.get('contentResult') and job['contentResult'][0].get('status') == 'completed' else {}

    job['securityEventPrevResult'] = job['securityEventPrevResult'][0] if job.get('securityEventPrevResult') and job['securityEventPrevResult'][0].get('status') == 'completed' else {}
    job['securityEventResult'] = job['securityEventResult'][0] if job.get('securityEventResult') and job['securityEventResult'][0].get('status') == 'completed' else {}

    job['changeCheckResult'] = job['changeCheckResult'][0] if job.get('changeCheckResult') and job['changeCheckResult'][0].get('status') == 'completed' else {}

    warnings = []
    target_status_list = []
    for t in ["vulResult", "sslResult", "contentResult", "securityEventResult", "changeCheckResult"]:
        if result := job[t].get("result", {}):
            target_status_list.append(result.get("targetStatus", {}).get("status", "good"))
            warnings.extend(result.get("warnings", []))
    score_info = Score.score_info({}, warnings)
    if "good" not in target_status_list and not warnings:
        score_info.update({"level": "未知"})
    job.update({"score_info": score_info})
    return jsonify({'data': {'jobOne': job}})


@api.route('/taskone', methods=['GET'])
def taskone():
    q = {'uid': g.audit_uid}
    task_id = request.args.get('taskId')
    assetId = request.args.get('assetId')
    refId = request.args.get('refId')
    if task_id:
        q['_id'] = ObjectId(task_id)
    _or = []
    if assetId or refId:
        if assetId:
            _or.append({'refId': ObjectId(assetId)})
        if refId:
            _or.append({'refId': ObjectId(refId)})
    if _or:
        q['$or'] = _or

    task = Task.objects.find(q).order_by('-id').first()
    if task:
        task = task.to_mongo().to_dict()
    else:
        task = {}
    return jsonify({'data': {'taskOne': task}})


@api.route('/updated-time', methods=['GET'])
def updated_time():
    """已完成的任务"""
    jobId = request.args.get('jobId')
    task = Task.objects.filter(jobId=ObjectId(jobId), status='completed').order_by('-endTime').first()
    if task:
        return jsonify({'data': {'endTime': task.endTime, '_id': task.id}})
    else:
        return jsonify({'data': {"endTime": "", "_id": ""}})


@api.route('/task_completed_times', methods=['GET'])
def task_completed_times():
    """已完成的任务"""
    job_id = request.args.get('job_id')
    asset_task_id = request.args.get('asset_task_id')
    task_types = request.args.get('task_types')

    if task_types not in TASK_TYPE_TUPLE:
        raise ParameterError(msg='参数错误')
    uid = g.audit_uid
    if g.role.name == '管理员':
        try:
            uid = request.args.get('uid')
            uid = ObjectId(uid)
        except Exception:
            raise ParameterError(msg='参数错误')

    task_id = None
    if asset_task_id:
        asset_task = AssetTasks.objects.find_one({"_id": ObjectId(asset_task_id)})
        if not asset_task:
            raise ParameterError(msg='该任务不存在')
        setting = getattr(asset_task, f"{task_types}Settings")
        task_id = setting.taskId

    query_dict = {"uid": uid, "status": "completed", "taskType": task_types}
    if task_id:
        query_dict["taskId"] = task_id
    if job_id:
        query_dict["jobId"] = ObjectId(job_id)
    tasks = Task.objects.find(query_dict).only("endTime", "id", "triggerType").order_by('-endTime')
    data = [{'time': task.endTime, 'id': task.id, 'trigger_type': task.triggerType} for task in tasks]
    return jsonify({'code': 200, 'data': data, 'status': 'success'})


@api.route('/ipv6', methods=['GET'])
def ipv6_reports_data():
    task_session_id = request.args.get('task_session_id')
    if not task_session_id:
        raise ParameterError(msg="task_session_id必填")
    query = {}
    if g.role.name in ['普通用户']:
        query['uid'] = g.user.id
    if not (
        task := Task.objects.filter(taskSessionId=task_session_id, **query).first()
    ):
        raise ParameterError(msg="查询任务不存在")
    event = ScanEvents.objects.filter(task_session_id=task_session_id).first()
    asset_task = AssetTasks.objects.find({"ipv6Settings.taskId": task.taskId}).first()
    availability = -1
    if not Job.objects.only('sourceIp').filter(pk=task.jobId).first().sourceIp:
        availability = get_ipv6_ava_results(task.uid, task.jobId).get('availability', 0)
    obj_user = User.objects.only('username').filter(pk=task.uid).first()
    event_detail = event.detail if event else {}
    event_detail, total_score = parse_ipv6_event(event_detail)
    data = {
        'task': {
            "taskSessionId": task.taskSessionId,
            "taskId": task.taskId,
            "name": task.name,
            "triggerType": task.triggerType,
            "enableMonitor": asset_task.enableMonitor
            if asset_task
            else task.taskSettings.enable,
            "jobId": task.jobId,
            "endTime": task.endTime,
            "target": task.target,
            "status": task.status,
            "oid": str(task.id),
            "username": obj_user.username if obj_user else '',
            "uid": str(task.uid),
        },
        'is_support_ipv6': task.result.addition.get('is_support_ipv6', False),
        'score': total_score,
        'event_detail': event_detail,
        'http_rate': availability,
    }
    return jsonify({'code': 200, 'results': data})


@api.route('/ipv6/support_check', methods=['GET'])
def ipv6_support_check():
    task_session_id = request.args.get('task_session_id')
    if not task_session_id:
        raise ParameterError(msg="task_session_id必填")
    query = {}
    if g.role.name in ['普通用户']:
        query['uid'] = g.user.id
    if not (
        task := Task.objects.filter(taskSessionId=task_session_id, **query).first()
    ):
        raise ParameterError(msg="查询任务不存在")
    event = ScanEvents.objects.filter(task_session_id=task_session_id).first()
    obj_user = User.objects.only('username').filter(pk=task.uid).first()
    event_detail = event.detail if event else {}
    event_detail = parse_ipv6_website_support_check(event_detail)
    detail = event_detail.get("ipv6_website_support_check") or {}
    data = {
        'task': {
            "taskSessionId": task.taskSessionId,
            "taskId": task.taskId,
            "name": task.name,
            "triggerType": task.triggerType,
            "jobId": task.jobId,
            "endTime": task.endTime,
            "target": task.target,
            "status": task.status,
            "oid": str(task.id),
            "username": obj_user.username if obj_user else '',
            "uid": str(task.uid),
        },
        'detail': detail,
    }
    return jsonify({'code': 200, 'results': data})


@api.route('/ipv6/support_check/detail', methods=['GET'])
def ipv6_support_check_detail():
    task_session_id = request.args.get('task_session_id')
    if not task_session_id:
        raise ParameterError(msg="task_session_id必填")
    page = int(request.args.get('page', 1))
    limit = int(request.args.get('limit', 10))
    depth = int(request.args.get('depth', 2))
    status = request.args.get('status')
    url_type = request.args.get('url_type')
    query = {}
    if g.role.name in ['普通用户']:
        query['uid'] = g.user.id
    if not Task.objects.filter(taskSessionId=task_session_id, **query).first():
        raise ParameterError(msg="查询任务不存在")
    detail_query = Q(task_session_id=task_session_id)
    if depth:
        detail_query = detail_query & Q(depth=depth)
    if status:
        status = int(status)
        if status == 1:
            detail_query = (
                detail_query & Q(status_code__gte=200) & Q(status_code__lt=300)
            )
        elif status == 2:
            detail_query = detail_query & (Q(status_code__gte=300) | Q(
                status_code__lt=200
            ))
    if url_type:
        url_type = int(url_type)
        if url_type == 1:
            detail_query = detail_query & Q(is_foreign_link=False)
        elif url_type == 2:
            detail_query = detail_query & Q(is_foreign_link=True)

    ipv6_check_urls = IPv6CheckUrls.objects.filter(detail_query).paginate(page, limit)
    total = ipv6_check_urls.total
    data_list = []
    for url_detail in ipv6_check_urls.items:
        data_list.append(
            {
                "url": url_detail.url,
                "is_foreign_link": url_detail.is_foreign_link,
                "referer": url_detail.referer,
                "depth": url_detail.depth,
                "is_redirect": url_detail.is_redirect,
                "redirect_url": url_detail.redirect_url,
                "ip": url_detail.ip,
                "status_code": url_detail.status_code,
                "url_type": url_detail.url_type,
                "request_method": url_detail.request_method,
                "request_headers": url_detail.request_headers,
                "response_version": url_detail.response_version,
                "response_headers": url_detail.response_headers,
                "created_at": arrow.get(url_detail.created_at).isoformat()
                if url_detail.created_at
                else "",
                "updated_at": arrow.get(url_detail.updated_at).isoformat()
                if url_detail.updated_at
                else "",
                "is_normal": True if 200 <= url_detail.status_code < 300 else False,
            }
        )
    data = {
        'data': data_list,
        'total': total,
    }
    return jsonify({'code': 200, 'results': data})

